package cn.youfule.mecs.netty.protocol;

import cn.youfule.mecs.netty.MecsProtocol;
import cn.youfule.mecs.netty.ProtocolField;
import cn.youfule.mecs.netty.util.CRC16Utils;
import cn.youfule.mecs.netty.util.ReflectionUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

@Slf4j
public abstract class AbstractDecoder<I extends AbstractProtocolContext> {

    public void decode(ChannelHandlerContext channelHandlerContext, ByteBuf buffer,List<Object> list) throws Exception {
        byte[] bytes = new byte[buffer.readableBytes()];
        for (int i = buffer.readerIndex();i < buffer.readableBytes();i++){
            bytes[i] = buffer.getByte(i);
        }
        log.debug("接收到的数据={}" ,CRC16Utils.bytesToHexString(bytes));


        int beginReader = buffer.readerIndex();

        MecsProtocol<I> prot = new MecsProtocol<>();
        buffer.readShort();//跳过包头
        prot.setId(readInt(buffer));
        prot.setFactory(readByte2Int(buffer));
        prot.setType(readByte2Int(buffer));
        prot.setFunctionCode(readByte2Int(buffer));

        prot.setContext(this.decodeContext(channelHandlerContext,prot,buffer));

        //读取当前索引作为crc之前所有byte的截止
        int endIndex = buffer.readerIndex();
        //标记当前位置
        buffer.markReaderIndex();
        //读取crc值
        prot.setCrc(readShort2Int(buffer));
        //索引恢复到标记点
        buffer.resetReaderIndex();
        //重新读取crc值为byte数组
        byte[] crc = new byte[]{buffer.readByte(),buffer.readByte()};
        //计算crc的值
        byte[] frame = new byte[endIndex - beginReader];
        buffer.getBytes(beginReader, frame, 0, endIndex - beginReader);
        byte[] actualCrc = CRC16Utils.CRC16(frame);
        log.debug(CRC16Utils.bytesToHexString(crc));
        if (actualCrc[0] != crc[0] || actualCrc[1] != crc[1]) {
            //交换高低位再次校验
            if(actualCrc[0] != crc[1] || actualCrc[1] != crc[0]) {
                String err = "CRC check failed!";
                log.error(err);
                throw new RuntimeException(err);
            }
        }
        list.add(prot);
    }

    /**
     * 是否支持该Protocol
     * @param buffer
     * @return
     */
    public boolean isSupport(ByteBuf buffer){
        // 刻度长度必须大于基本最小长度
        if(buffer.readableBytes() >= supportMinLength()){
            //防止socket字节流攻击、客户端传来的数据过大，这里需要对数据进行过滤掉
            if(buffer.readableBytes() >= 4096){
                buffer.skipBytes(buffer.readableBytes());
                return false;
            }

            //记录包头开始位置
            int beginReader = 0;
            while(true){
                beginReader = buffer.readerIndex(); //记录包头开始位置
                //buffer.markReaderIndex(); //标记包头开始index
                //读取协议开始标志
                if(buffer.getShort(beginReader) == MecsProtocol.START){
                    break; //如果是开始标记，那么就结束查找
                }

                //如果找不到包头，这里要一个一个字节跳过
                //buffer.resetReaderIndex();
                buffer.readByte();

                //当跳过后，如果数据包又不符合长度的，结束本次协议解析
                if(buffer.readableBytes() < supportMinLength()){
                    return false;
                }
            }

            //获取功能码是否支持
            int functionCode = buffer.getByte(beginReader + 8);
            for (byte fc: this.supportFunctionCode()){
                if(functionCode == fc){
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 支持的最小长度
     * @return
     */
    protected abstract int supportMinLength();

    /**
     * 支持的功能码
     * @return
     */
    protected abstract byte[] supportFunctionCode();

    /**
     * 编码协议数据内容部分
     * @param channelHandlerContext
     * @param prot
     * @param buffer
     */
    protected I decodeContext(ChannelHandlerContext channelHandlerContext, MecsProtocol<I> prot, ByteBuf buffer) throws Exception {
        Class<I> clazz = (Class<I>) ReflectionUtil.getTypeArgumentClassList(this.getClass());
        I context = clazz.newInstance();
        if(buffer.readableBytes() < 4){
            return context;
        }
        context.setStartIndex(readByte2Int(buffer));
        context.setContextLength(readByte2Int(buffer));
        if(context.getContextLength() == 0){
            return context;
        }
        if(buffer.readableBytes() < context.getContextLength() + 2){
            throw new RuntimeException("数据内容与数据长度不匹配");
        }

        List<Field> fieldList = new ArrayList<>();
        //field所有字段设置的长度和;buffer指明的长度 context.getContextLength();buffer实际长度 buffer.readableBytes() - 2;
        int totalLength = 0;
        // 获取类模板
        Class c = context.getClass();
        // 获取所有字段
        for(Field f : c.getDeclaredFields()){
            // 判断这个字段是否有MyField注解
            if(f.isAnnotationPresent(ProtocolField.class)){
                fieldList.add(f);
                totalLength += f.getAnnotation(ProtocolField.class).length();
            }
        }
        //list排序
        fieldList.sort(Comparator.comparing(com -> com.getAnnotation(ProtocolField.class).start()));
        for (Field f:fieldList ){
            ProtocolField anno = f.getAnnotation(ProtocolField.class);
            int length = anno.length();
            int start = anno.start();

            //如果字段开始索引小于开始索引，则跳过字段
            if(start < context.getStartIndex()){
                continue;
            }
            //字段开始索引已经超出当前读取索引，则结束
            if(start >= context.getStartIndex()+context.getContextLength()){
                break;
            }
            //字段所需字节长度大于剩余的数据内容字节长度，则结束 -2是除去crc校验的两个长度
            if(length > buffer.readableBytes() - 2){
                //跳过剩余的几个字节，从而保证crc校验位能对的上号
                buffer.skipBytes(buffer.readableBytes() - 2);
                break;
            }
            //如果字段开始索引在当前读取索引之后，则需要跳过字节 buffer.readerIndex() - 9为数据内容byte[]的实际索引
            while (start > context.getStartIndex() + buffer.readerIndex() - 9){
                //一个字节一个字节的跳过
                buffer.readByte();
            }

            if(!f.isAccessible()){
                f.setAccessible(true);
            }
            Class type = f.getType();
            boolean noWrite = false;
            try {
                if(length == 1){
                    f.set(context,readByte2Int(buffer));
                }else if(length == 2){
                    if (type == LocalTime.class){
                        f.set(context,readLocalTime(buffer));
                    }else if (type ==int.class || type == Integer.class ||
                            type == short.class || type == Short.class){
                        f.set(context,readShort2Int(buffer));
                    }else{
                        noWrite = true;
                    }
                }else if(length == 3){
                    if (type == LocalDate.class){
                        f.set(context,readLocalDate(buffer));
                    }else{
                        noWrite = true;
                    }
                }else if(length == 4){
                    if (type ==int.class || type == Integer.class){
                        f.set(context,readInt(buffer));
                    }else{
                        noWrite = true;
                    }
                }else if (length == 6){
                    if(type == LocalDateTime.class){
                        f.set(context,readLocalDateTime(buffer));
                    }else{
                        noWrite = true;
                    }
                }

                //如果上面没有匹配，则写入byte[length]
                if(noWrite){
                    f.set(context,buffer.readBytes(length));
                }
            } catch (IllegalAccessException e) {
                log.error("协议解码异常",e);
                throw new RuntimeException("协议解码异常");
            }

        }

        /*Arrays.stream(c.getDeclaredFields()).filter(f -> f.isAnnotationPresent(ProtocolField.class))
                .sorted(Comparator.comparing(com -> com.getAnnotation(ProtocolField.class).index()))
                .map(c -> {

                })
        ;*/
        return context;
    }

    private int readByte2Int(ByteBuf buf){
        return buf.readByte() & 0xff;
    }
    private int readShort2Int(ByteBuf buf){
        byte h = buf.readByte();
        byte l = buf.readByte();
        return l & 0xFF |  (h & 0xFF) << 8;
    }
    private int readInt(ByteBuf buf){
        byte b1 = buf.readByte();
        byte b2 = buf.readByte();
        byte b3 = buf.readByte();
        byte b4 = buf.readByte();
        return b4 & 0xFF | (b3 & 0xFF) << 8 | (b2 & 0xFF) << 16 | (b1 & 0xFF) << 24;
    }
    private LocalTime readLocalTime(ByteBuf buf){
        int hour = readByte2Int(buf);
        int minute = readByte2Int(buf);
        try {
            return LocalTime.of(hour,minute);
        }catch (Exception e){
            return LocalTime.of(0,0);
        }
    }
    private LocalDate readLocalDate(ByteBuf buf){
        int year = readByte2Int(buf);
        int month = readByte2Int(buf);
        int day = readByte2Int(buf);
        try {
            return LocalDate.of(year,month,day);
        }catch (Exception e){
            return LocalDate.of(0,0,0);
        }
    }
    private LocalDateTime readLocalDateTime(ByteBuf buf){
        int year = readByte2Int(buf);
        int month = readByte2Int(buf);
        int day = readByte2Int(buf);
        int hour = readByte2Int(buf);
        int minute = readByte2Int(buf);
        int second = readByte2Int(buf);
        try {
            return LocalDateTime.of(year,month,day,hour,minute,second);
        }catch (Exception e){
            return LocalDateTime.of(0,0,0,0,0,0);
        }
    }
}
